package dk.itu.mobile.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Date;
import java.util.LinkedList;
import java.util.Queue;

import dk.itu.mobile.util.CryptoBaws;
import dk.itu.mobile.util.TaskListSerializer;
import dk.itu.mobile.vo.QueueItem;
import dk.itu.mobile.vo.Status;
import dk.itu.mobile.vo.Task;
import dk.itu.mobile.vo.TaskList;


/**
 * @author Group-02
 * 
 * TaskServer maintains a tasklist and awaits request from Clients using UDP protocol
 * When a request is received, it is put in a Queue (TaskQueue) for processing.
 * A TaskQueue (inner class) tries to process all incoming requests and if not succesfull
 * the requests are dequeued for later processing.
 * If a request can not be processed because of a deadlock, it will be aborted after 10 unsuccesfull tries.
 * 
 *  
 */

@SuppressWarnings("deprecation")
public class TaskServer {
	protected DatagramSocket socket = null;
	protected BufferedReader in = null;
	protected boolean moreRequests = true;
	private TaskList taskList;
	private TaskQueue taskQueue;

	private static int expireLimit = 10000; // time in millisecs.

	private String tokenSecret = "super1337secret!";


	public TaskServer() throws IOException {
		this("TaskServerThread");
	}

	public TaskServer(String name) throws IOException {
		socket = new DatagramSocket(4445);
		taskList = TaskListSerializer.load("data/task-manager-revised.xml",
				TaskList.class);
	}

	public void start() {
		System.out.println("Starting Server");

		// Keeps waiting for incoming requests from Clients
		while (moreRequests) {
			try {
				byte[] buf = new byte[256];

				// receive request
				DatagramPacket packet = new DatagramPacket(buf, buf.length);
				socket.receive(packet);
				String request = new String(packet.getData());

				System.out.println(request);

				String roleTimestamp = request.substring(0, request.indexOf(",")).trim();


				//Decrypt role and timestamp
				roleTimestamp = CryptoBaws.decode(roleTimestamp, tokenSecret);
				System.out.println("Incoming Client role and timestamp: " + roleTimestamp);

				String taskdata = request.substring(request.indexOf(",") + 1).trim();
				System.out.println("Incoming Client request queued: " + taskdata);

				// get address and port from Client
				InetAddress address = packet.getAddress();
				int port = packet.getPort();

				// TODO decrypt and read values from incoming request
				System.out.println(request);
				String role = roleTimestamp.substring(0, roleTimestamp.indexOf(",")).trim();
				System.out.println("role: " + role);

				String date = roleTimestamp.substring(roleTimestamp.indexOf(",") + 1).trim();
				Date timeStamp = new Date();

				timeStamp.setTime(Long.parseLong(date));

				System.out.println("timestamp: " + timeStamp.toGMTString());

				//create queueItem and add to queue
				QueueItem q = new QueueItem(taskdata, address, port, role, timeStamp);
				taskQueue.addItem(q);

			} catch (IOException e) {
				e.printStackTrace();
				moreRequests = false;
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		socket.close();
	}

	/**
	 * A TaskQueue, maintains queue of requests and process all incoming request and if not succesfull
	 * a request is dequeued again for later processing.
	 * If a request can not be processed because of a deadlock, it will be aborted after a number of unsuccesfull tries.
	 *
	 */

	private class TaskQueue implements Runnable {
		private Queue<QueueItem> queue;
		public DatagramSocket socket;
		private int maxloop = 10;

		@Override
		public void run() {
			queue = new LinkedList<QueueItem>();

			initiateRunQueueIteration();			
		}

		public void addItem(QueueItem item) {
			queue.offer(item);
		}

		public void initiateRunQueueIteration() {
			while (true) {
				if(queue.peek() != null) { 
					// try to process Item;
					QueueItem queueItem = queue.poll();

					try {
						byte[] buffer = new byte[256];
						DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
						InetAddress address = queueItem.getIAddress();
						int port = queueItem.getPort();

						// Try to process the requestItem
						String result = process(queueItem);
						System.out.println(result);
						buffer = result.getBytes();

						// send the response to the client at "address" and "port"
						packet = new DatagramPacket(buffer, buffer.length, address, port);
						socket.send(packet);

					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}

				} else {
					try {
						System.out.println("Nothing in the queue, waiting 5 seconds");
						Thread.sleep(2500);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			} 
		}

		private synchronized String process(QueueItem item) {
			System.out.println("Proccessing");
			String message = "";
			String request = item.getRequest();
			String itemRole = item.getRole();
			long expiration = item.getTimeStamp().getTime() + expireLimit;

			Date currentTime = new Date();

			System.out.println(expiration - currentTime.getTime());
			
			// Check validity of request
			if (taskList.getTask(request) == null) {
				message += "No such task exists ";
			}
		
			// check expiration
			else if (expiration - currentTime.getTime() < 0  ) {
				message += "Time limit expired";	
			}

			// else get task object and check role
			else {
				Task task = taskList.getTask(request);

				// check role
				boolean roleCheck = false;

				for (String role: task.getRoles() ) {
					System.out.println(role);
					if (role.trim().equalsIgnoreCase(itemRole)) {
						
						roleCheck = true;
					}
				}
	
				if (!roleCheck) {		
					// If role credentials not OK
					message += "Error: Your role doesn't allow you to perform this task!";
				} 
				
				// role and time-stamp approved
				else {
					// check if conditions has been executed or are required
					if (!task.conditions.equals("")) {
						String[] conditions = task.conditions.split(",");
						Task cTask;
						// check all conditions
						for (String c : conditions) {
							cTask = taskList.getTask(c);
							// return error if not executed
							if (cTask.status == Status.NOT_EXECUTED)
								message += "Task: " + c + " must be executed first \n";

							// return error if condition task is required by other
							if (cTask.required)
								message += "Task: " + c + " is required by other task \n";
						}
					}

					

					if (message != "") {
						if (item.tries++ > maxloop ) {
							message += "Task has been aborted due to " + maxloop + " unsuccesfull tries.. ";
						}

						// Put Item back in queue
						else {
							taskQueue.addItem(item);
							message += "Task is re-queued and will be processed later..";
						}
						return message;

					}

					else {
						// Task ready for processing
						task.status = Status.EXECUTED;
						task.required = false;
						// set response tasks as required
						if (!task.responses.equals("")) {
							String[] responses = task.responses.split(",");
							Task rTask;
							for (String responser : responses) {
								rTask = taskList.getTask(responser);
								rTask.required = true;
							}
						}
						message = "request: " + task.id.toString() + " will be processed";
					}
				}
			}			
			// return message to client
			return message;
		}
	}


	// Initiates a server and a TaskQueue in seperate threads.
	public static void main(String[] args) throws IOException {
		TaskServer server = new TaskServer();
		server.taskQueue = server.new TaskQueue();
		server.taskQueue.socket = server.socket;

		Thread queueThread = new Thread(server.taskQueue);
		queueThread.start();

		server.start();
	}

}


